 1.Zookeeper应用实践
   
   ZooKeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架，我们可以使⽤用它来进行分布式
数据的发布与订阅。另一方面，通过对ZooKeeper中丰富的数据节点类型进⾏行行交叉使⽤用，配合Watcher
事件通知机制，可以非常方便地构建一系列列分布式应用中都会涉及的核⼼心功能，如数据发布/订阅、命名
服务、集群管理、Master选举、分布式锁和分布式队列列等。那接下来就针对这些典型的分布式应⽤用场景
来做下介绍
  Zookeeper的两大特性：
  1).客户端如果对Zookeeper的数据节点注册Watcher监听，那么当该数据节点的内容或是其⼦子节点
列列表发⽣生变更更时，Zookeeper服务器器就会向订阅的客户端发送变更更通知。
  2)对在Zookeeper上创建的临时节点，⼀一旦客户端与服务器器之间的会话失效，那么临时节点也会被
⾃自动删除
  利用其两⼤大特性，可以实现集群机器器存活监控系统，若监控系统在/clusterServers节点上注册一个
Watcher监听，那么但凡进⾏行行动态添加机器器的操作，就会在/clusterServers节点下创建⼀一个临时节
点：/clusterServers/[Hostname]，这样，监控系统就能够实时监测机器器的变动情况。
 
 2.服务器动态上下线监听
   
   分布式系统中，主节点会有多台，主节点可能因为任何原因出现宕机或者下线，而任意一台客户端都要
能实时感知到主节点服务器器的上下线。
   具体实现
   服务端
package com.lagou.zk.onoffline;

import org.I0Itec.zkclient.ZkClient;

//服务端主要提供了client需要的一个时间查询服务，服务端向zk建立临时节点
public class Server {

    //获取zkclient
    ZkClient zkClient =null;
    private void connectZk() {
        //创建zkclient
        zkClient = new ZkClient("linux121:2181, linux122:2181");
        //创建服务器建立临时节点的目录
        if(!zkClient.exists("/servers")) {
            zkClient.createPersistent("/servers");
        }
    }
    //告知zk服务器相关信息
    private void saveServerInfo(String ip, String port) {

        final String sequencePath = zkClient.createEphemeralSequential("/servers/server", ip + ":" + port);
        System.out.println("------>>>,服务器：" + ip + ":" + port +",向zk保存信息成功，成功上线可以接受client查询");
    }

    public static void main(String[] args) {
        //准备两个服务端启动上线(多线程模拟，一个线程代表一个服务器)
        final Server server = new Server();
        server.connectZk();
        server.saveServerInfo(args[0], args[1]);
        //提供时间服务线程没有启动，创建一个线程类，可以接收socket请求
        new TimeService(Integer.parseInt(args[1])).start();
    }
}

   服务端提供时间查询的线程类
package com.lagou.zk.onoffline;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

//提供时间服务
public class TimeService extends Thread {
    private int port =0;

    public TimeService(int port) {
        this.port = port;
    }

    @Override
    public void run() {
        //通过socket与client进行交流，启动serversocket监听请求
        try {
            //指定监听的端口
            final ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                final Socket socket = serverSocket.accept();
                //不关心client发送内容，server只考虑发送一个时间值
                final OutputStream out = socket.getOutputStream();
                out.write(new Date().toString().getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

   client端
package com.lagou.zk.onoffline;

import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

//注册监听zk指定目录
//维护自己本地一个servers信息，收到通知要进行更新
//发送时间查询请求并接受服务器返回的数据
public class Client {
    //获取zkclient
    ZkClient zkClient =null;
    //维护一个servers信息集合
    ArrayList<String> infos = new ArrayList<String>();
    private void connectZk() {
        //创建zkclient
        zkClient = new ZkClient("linux121:2181, linux122:2181");
        //第一次获取服务信息
        final List<String> childs = zkClient.getChildren("/servers");
        for (String child : childs) {
            //存储着ip+port
            final Object o = zkClient.readData("/servers/" + child);
            infos.add(String.valueOf(o));
        }
        //对servers目录监听
        zkClient.subscribeChildChanges("/servers", new IZkChildListener() {
            public void handleChildChange(String s, List<String> children) throws Exception {
                //接收到通知，说明节点发送了变化，client需要更新infos集合中的数据
                ArrayList<String> list = new ArrayList<String>();
                //遍历更新过后的所有节点信息
                for (String path : children) {
                    final Object o = zkClient.readData("/servers/" + path);
                    list.add(String.valueOf(o));
                }
                //最新数据覆盖老数据
                infos=list;
                System.out.println("---》接收到通知，最新服务器信息为："+infos);
            }
        });
    }

    //发送时间查询的请求
    public void sendRequest() throws IOException {
        //目标服务器地址
        final Random random = new Random();
        final int i = random.nextInt(infos.size());
        final String ipPort = infos.get(i);
        final String[] arr = ipPort.split(":");
        //建立socket连接
        final Socket socket = new Socket(arr[0], Integer.parseInt(arr[1]));
        final OutputStream out = socket.getOutputStream();
        final InputStream in = socket.getInputStream();
        //发送数据
        out.write("query time".getBytes());
        out.flush();
        //接收返回结果
        byte[] b = new byte[1024];
        in.read(b);//读取服务器返回数据
        System.out.println("client端接收到server端:" + ipPort +"返回结果："+new String(b));

        //释放资源
        in.close();
        out.close();
        socket.close();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        final Client client = new Client();
        client.connectZk();//监听器逻辑
        while (true) {
            client.sendRequest();//发送请求
            //每隔几秒钟发送一次请求到服务端
            Thread.sleep(2000);
        }

    }
}
